#include "Sistema.h"
#include "Mensajero.h"
#include "ColaConcurrente.h"
#include "Debug.h"

#include <sys/types.h>
#include <signal.h>

#include <iostream>
#include <locale>
#include <algorithm>
#include <limits>

#define CYAN "\033[0;36m"
#define LIGHT_CYAN "\033[1;36m"

#define RED "\033[31m"
#define LIGHT_RED "\033[1;31m"

#define GRAY "\033[1;30m"

#define WHITE "\033[1;37m"

#define CYAN_BACK "\033[0;46m"
#define LIGHT_CYAN_BACK "\033[1;46m"

#define RED_BACK "\033[0;41m"
#define LIGHT_RED_BACK "\033[1;41m"

#define WHITE_BACK "\033[1;44m"

#define NO_COLOUR "\033[0m"


Sistema::Sistema(){

	intervaloActualizacion = 3000000; //3 segundos
	srand(time(NULL));

	mensajero = new Mensajero;

	try{
		cola = new ColaConcurrente<Mensaje>;
	}
        catch(GenericException &e){
		delete mensajero;
		throw;
	}
}


void Sistema::dibujarTableros(){

	std::cout << "\n";
	dibujarLinea(45);
	std::cout << "   ";
	dibujarLinea(45);
	std::cout << std::endl;
	
	for(int i=0;i<2;i++){
		std::cout << "|   |";
		for(char c='A';c<='J';c++){
			std::cout << " " << c << " |";
		}
		std::cout << "   ";
	}
	std::cout << "\n";
	
	for(int j=0;j<ALTO_TABLERO;j++){
		dibujarLinea(45);
		std::cout << "   ";
		dibujarLinea(45);
		std::cout << std::endl;


		for(int k=0;k<2;k++){
			std::cout << "| " << j+1;
			(j+1<10)? 
				std::cout << " " :
				std::cout << "";

			int tablero = k==0?TABLERO_PROPIO:TABLERO_OPONENTE;

			for(int i=0;i<ANCHO_TABLERO;i++){
				std::cout << "|";
				
				char c = jugador[tablero].getCasillero(i,j);
				switch(c){
				case AGUA: 
					std::cout <<  WHITE_BACK << "   " << NO_COLOUR;
					break;
				case BARCO:
					std::cout  << WHITE << WHITE_BACK << "~^~" << NO_COLOUR;
					break;
				case BARCO_HUNDIDO:
					std::cout << RED << WHITE_BACK << " X " << NO_COLOUR;
					break;
				case DISPARO:
					std::cout << CYAN  << WHITE_BACK << " o " << NO_COLOUR;
					break;
				default:
					std::cout << " ? ";
					break;
				}
			}
			std::cout << "|   ";
		}
		std::cout << "\n";

	}
	dibujarLinea(45);
	std::cout << "   ";
	dibujarLinea(45);
	std::cout << std::endl;

}


void Sistema::mostrarAyuda(){
	std::cout << "Comandos disponibles:";
	std::cout << "\n\
           ?        ...\n\
           p        Imprime por pantalla el tablero.\n\
           d x y    Efectua un disparo al oponente.\n\
           q        Sale del programa.\n";
}

void Sistema::ubicarBarcos(){

	dibujarTableros();

	char c;
	do{
		std::cout << "¿Desea posicionar sus barcos manualmente o automáticamente?(M/A)";
		std::cin >> c;
		c = tolower(c);
	}while(c != 'm' && c != 'a');

	
	//Posiciono los barcos automáticamente
	if(c=='a'){

		srand(time(NULL));

		while(jugador[TABLERO_PROPIO].getCantidadBarcos() < MAX_BARCOS){
			int x = rand()%ANCHO_TABLERO;
			int y = rand()%ALTO_TABLERO;
			jugador[TABLERO_PROPIO].ubicarBarco(x,y);
		}

		return;
	}

	//posiciono los barcos manualmente
	std::cout << "A continuación, ingrese las coordenadas de sus 10 barcos.\n";

	while(jugador[TABLERO_PROPIO].getCantidadBarcos() < MAX_BARCOS){
		char coordenadaX;
		int coordenadaY;
		bool error;

		std::string linea;
	
		std::getline(std::cin,linea);

		//Transformo a minusculas
		transform(linea.begin(), linea.end(), linea.begin(), tolower);

		//Borro espacios en blanco
		while(linea.size()>0)
			if(linea[0] == ' ')
				linea.erase(0,1);
			else break;
		
		if(linea.size() == 0)
			continue;

		error = false;

		if(isalpha(linea[0])){
			coordenadaX = toupper(linea[0]);
			
			const char *string = linea.c_str()+1;
			char *final;
			
			coordenadaY = strtol(string, &final, 10);
			if(final == string || coordenadaY==0)
				//No es un numero
				error=true;
			
		}
		else error=true;
		
		if (error){
			std::cout << "La sintaxis es: <cordenada x> <coordenada y>\n";
		}

		else{
			int x=coordenadaX-'A';
			int y=coordenadaY-1;
			if(jugador[TABLERO_PROPIO].ubicarBarco(x,y)==0)
				dibujarTableros();
			else
				std::cout << "Coordenada inválida.\n";
		}
	}
}

void Sistema::determinarTurnos(){

	std::cout << "Para determinar el orden de los jugadores, se requiere que cada uno \
ingrese un numero del 1 al 100. Quien esté mas cerca de un numero \
elegido al azar comenzará la partida.\n";

	int numero;

	//Leo un numero
	while(!(std::cin >> numero))
	{
		//Si no es numero hay que limpiar el buffer y volver a intentar
		std::cin.clear();
		std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
	}

	Mensaje m;

	if(mensajero->getMaster()){
		//Si soy el master, debo generar un numero al azar
		//para comparar los resultados.
		int generado = rand()%100+1;
		m = mensajero->recibir();

		int distancia1 = abs(numero-generado);
		int distancia2 = abs((int)m.contenido[1] - generado);

		m.contenido[0]=COMANDO_TURNO;

		if(distancia1 <= distancia2){
			m.contenido[1]=1; //Empieza esta instancia
			tengoTurno = true;
		}
		else{
			m.contenido[1] = 0;  //Empieza la otra instancia
			tengoTurno = false;
		}

		mensajero->enviar(m);
	}
	else{
		m.contenido[0] = COMANDO_DECIDIR_TURNO;
		m.contenido[1] = numero;
		mensajero->enviar(m);
		m = mensajero->recibir();
		if(m.contenido[1] == 0)
			tengoTurno = true;
		else tengoTurno = false;
	}

	std::dbg << "Esta instancia tiene el ";
	std::cout << "Esta instancia tiene el ";
	if(tengoTurno){
		std::dbg << "primer";
		std::cout << "Primer";
	}
	else{
		std::dbg << "segundo";
		std::cout << "segundo";
	}
	std::dbg << " turno.\n";
	std::cout << " turno.\n";
}

void Sistema::loopPrincipal(){

	std::cout << "Esperando que se conecte otro jugador...\n";

	mensajero->conectarse();

	std::cout << "Conexion exitosa.\n";

	ubicarBarcos();

	determinarTurnos();

	int pidTeclado=fork();

	if(pidTeclado == 0){
		//Proceso que lee comandos de teclado.
		std::dbg << "Creo el proceso para el teclado.\n";

		procesarTeclado();

		exit(0);
	}

	int pidTablero=fork();

	if(pidTablero==0){
		//Proceso que actualiza el tablero
		std::dbg << "Creo el proceso para el tablero.\n";

		while(true){
			
			//Pido el nuevo estado del tablero.
			std::dbg << "Actualizo el tablero.\n";
			Mensaje m;
			m.borrar();
			m.contenido[0] = COMANDO_OBTENER_TABLERO;
			mensajero->enviar(m);
			usleep(intervaloActualizacion);
		}
	}

	int pidMensajero = fork();
	if(pidMensajero==0){
		//Proceso que recibe mensajes.
		std::dbg << "Entro al loop del mensajero.\n";
		try{
			Mensaje m;
			do{
				m = mensajero->recibir();
				cola->push(m);
			}while(true);
		}
		catch(GenericException &e){
		}
		std::dbg << "Salgo del loop del mensajero\n";
		exit(0);
	}

	try{
		//Loop principal. Procesa mensajes recibidos del otro jugador
		//y del teclado y envía mensajes al otro jugador
		bool salir=false;
		Mensaje m;
		do{
			m = cola->pop();
			bool local = (m.tipo == COMANDO_LOCAL);

			switch(m.contenido[0]){

			case COMANDO_DISPARO:
				//Recibo/Envio disparo
				if(local){
					if(!tengoTurno){
						std::cout << "No tengo el turno.\n";
						break;
					}

					if(jugador[TABLERO_OPONENTE].getCasillero((char)*(m.contenido+1),(int)*(m.contenido+1+sizeof(char)) -1) != AGUA){
						std::cout << "Ese casillero ya fue bombardeado.\n";
						break;
					}

					//Envío el disparo
					mensajero->enviar(m);

					//Pido el nuevo estado del tablero.
					m.borrar();
					m.contenido[0] = COMANDO_OBTENER_TABLERO;
					mensajero->enviar(m);
					
					//Paso el turno
					m.borrar();
					m.contenido[0] = COMANDO_PASAR_TURNO;
					tengoTurno=false;
					mensajero->enviar(m);
				}
				else{
					//Recibo el disparo
					recibirDisparo((char)*(m.contenido+1),(int)*(m.contenido+1+sizeof(char)) -1 );
					dibujarTableros();
					if(jugador[TABLERO_PROPIO].getCantidadBarcos() == 0){
						std::cout << "Has perdido.\n";
						
						//Aviso al otro jugador que ganó
						m.borrar();
						m.contenido[0] = COMANDO_GANAR;
						mensajero->enviar(m);

						//Me desconecto
						m.contenido[0] = COMANDO_DESCONECTAR;
						mensajero->enviar(m);

						//Termino el proceso
						cola->push(m);
					}
				}
				break;
			
			case COMANDO_OBTENER_TABLERO:
				//Muestro/envío tablero
				if(local)
					dibujarTableros();
				else{
					m.borrar();
					m.contenido[0] = COMANDO_CONTENIDO_TABLERO;
					for(int j=0;j<jugador[TABLERO_PROPIO].getAlto();j++){
						for(int i=0;i<jugador[TABLERO_PROPIO].getAncho();i++){
							char casillero = jugador[TABLERO_PROPIO].getCasillero(i,j);
							m.contenido[1+i*jugador[TABLERO_PROPIO].getAncho()+j] = casillero==BARCO?AGUA:casillero;
						}
					}
					mensajero->enviar(m);
				}
				break;

			case COMANDO_CONTENIDO_TABLERO:

				for(int j=0;j<jugador[TABLERO_OPONENTE].getAlto();j++){
					for(int i=0;i<jugador[TABLERO_OPONENTE].getAncho();i++){
						char casillero = m.contenido[1+i*jugador[TABLERO_OPONENTE].getAncho()+j];
						jugador[TABLERO_OPONENTE].setCasillero(i,j, casillero);
					}
				}
				
				dibujarTableros();

				break;

			case COMANDO_PASAR_TURNO:
				tengoTurno=true;
				break;

			case COMANDO_GANAR:
				std::cout << "Has ganado. ";
				int cantidadBarcos;
				cantidadBarcos = jugador[TABLERO_PROPIO].getCantidadBarcos();
				if(cantidadBarcos == 1)
					std::cout << "Te ha quedado 1 barco.\n";
				else std::cout << "Te han quedado " << cantidadBarcos << " barcos.\n";

				//Sin break, dejo que siga de largo y se desconecte.

			case COMANDO_DESCONECTAR:
				kill(pidTablero,SIGKILL);
				kill(pidTeclado, SIGKILL);
				kill(pidMensajero, SIGKILL);

				salir = true;
				break;
			default:
				break;
			}
		
		}while(!salir);

		std::dbg << "Salgo del loop principal.\n";
	}
	catch(GenericException &e){
		kill(pidTablero,SIGKILL);
		kill(pidTeclado, SIGKILL);
		kill(pidMensajero, SIGKILL);
		throw;
	}
}

void Sistema::procesarTeclado(){
	bool salir=false;

	std::cout << "Para obtener ayuda ingrese el comando ?\n";

	while(!salir){

		std::string linea;
	
		std::getline(std::cin,linea);

		//Transformo a minusculas
		transform(linea.begin(), linea.end(), linea.begin(), tolower);

		//Borro espacios en blanco
		while(linea.size()>0)
			if(linea[0] == ' ')
				linea.erase(0,1);
			else break;
		
		if(linea.size() == 0)
			continue;

		Mensaje m;

		switch(linea[0]){
		case 'q':
			m.tipo = COMANDO_REMOTO;
			m.contenido[0] = COMANDO_DESCONECTAR;
			cola->push(m);
			m.tipo = COMANDO_LOCAL;
			m.contenido[0] = COMANDO_DESCONECTAR;
			cola->push(m);

			break;

		case 'p':
			m.tipo = COMANDO_LOCAL;
			m.contenido[0] = COMANDO_OBTENER_TABLERO;
			cola->push(m);

			break;

		case 'd':
			char coordenadaX;
			int coordenadaY;
			bool error;
			int posicion;

			error = false;

			//Salteo espacios
			for(posicion=1;posicion<linea.size()&&linea[posicion]==' ';posicion++);

			if(posicion < linea.size() && isalpha(linea[posicion])){
				coordenadaX = toupper(linea[posicion]);

				const char *string = linea.c_str()+posicion+1;
				char *final;

				coordenadaY = strtol(string, &final, 10);
				if(final == string || coordenadaY==0)
					//No es un numero
					error=true;

			}
			else error=true;

			if (error){
				std::cout << "La sintaxis es: d <cordenada x> <coordenada y>\n";
			}
			else{
				
				if(!jugador[TABLERO_OPONENTE].comprobarLimites(coordenadaX-'A', coordenadaY-1)){
					std::cout << "No es una coordenada válida.\n";
				}
				else{
					std::dbg << "Disparo a: " << coordenadaX << "," << coordenadaY << "\n";
					m.tipo = COMANDO_LOCAL;
					m.contenido[0] = COMANDO_DISPARO;
					*(m.contenido+1) = coordenadaX -'A';
					*(m.contenido+1+sizeof(coordenadaX)) = coordenadaY;
					cola->push(m);
				}
			}
			
			break;

		case '?':
			mostrarAyuda();
			break;

		default:
			std::cout << linea << ": comando invalido. Intente '?'\n";
			break;
		}
	}
}
